﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Solidi
{
	sealed class DependencyObject
	{
		private IDependency _dependency;
		private object _token;
		private int _references;
		private bool _created;
		private object _instance;
		private IEnumerable<object> _children;
		private int _pins;
        private object _locker;
	
		public DependencyObject(IDependency dependency, object token, object locker)
		{
			_dependency = dependency;
			_token = token;
            _locker = locker;
		}

		public void Pin()
		{
			Interlocked.Increment(ref _pins);
		}

		public void Unpin()
		{
			Interlocked.Decrement(ref _pins);
		}

		public bool Pinned
		{
			get
			{
				return _pins > 0;
			}
		}

		public object Locker
		{
			get
			{
                return _locker;
			}
		}

		public bool Aquire(IDependencyContainer container, out object instance)
		{
			instance = null;

			try
			{
				if (!_created)
				{
					_created = true;

					DependencyObjectLifetime lifetime = new DependencyObjectLifetime(container);

					container = new DependencyContainer(container.Registry, container.Resolver, container.Composer, lifetime);

					_instance = _dependency.Factory(container, _dependency);

					// children could be updated after lifetime has gone out of scope by say a Func<> dependency created as a child

					_children = lifetime.Children;

					return true;
				}

				return false;
			}
			finally
			{
				instance = _instance;

				if (instance != null)
				{
					_references++;
				}
			}
		}
		
		public bool Discard(IDependencyContainer container, out bool found)
		{
			found = false;

			if (_created)
			{
				found = _references > 0;

				if (found && --_references == 0 && DiscardToken())
				{
					Destroy(container);

					return true;
				}
			}
			
			return false;
		}

		private bool DiscardToken()
		{
			return _dependency.Lifestyle.DiscardToken(_token);
		}

		private void Destroy(IDependencyContainer container)
		{
			_created = false;

			if (_instance != null)
			{
				foreach (object child in _children)
				{
					container.Resolver.TryRelease(container, child);
				}

				container.Composer.TryDestroy(_instance);

				_instance = null;
				_children = null;
			}
		}

		public bool DiscardAll(IDependencyContainer container, bool disposing)
		{
			if (_created)
			{
				if ((_references > 0 && DiscardToken()) || disposing)
				{
					Destroy(container);

					return true;
				}
				else
				{
					_references = 0;
				}
			}
			
			return false;
		}

		public override bool Equals(object obj)
		{
			DependencyObject token = obj as DependencyObject;

			if (token != null)
			{
				return Object.ReferenceEquals(_dependency, token._dependency) && Object.ReferenceEquals(_token, token._token);
			}

			return base.Equals(obj);
		}

		public override int GetHashCode()
		{
			return _dependency.GetHashCode() ^ _token.GetHashCode();
		}
	}
}
